#include "QxGenericShader.hpp"

#include <QMap>
#include <QFile>
#include <QVector>
#include <QMessageBox>
#include <QTextStream>

struct ProgramData{
  GLuint shaderType;
  GLuint shaderID;

  ProgramData( GLuint vType=0, GLuint vID=0 ) :  shaderType( vType ), shaderID( vID ) {
  }
};

class QxGenericShader::QxGenericShaderPrivate
{
public:

  QxGenericShaderPrivate( QxGenericShader* me ) : m_self( me ) {
  }

  ~QxGenericShaderPrivate() {
    Q_FOREACH( GLuint programID, m_programs.keys() ) {
      m_self->deleteProgram( programID );
    }
  }

  int queryShader( GLuint programID, GLuint shaderType ) {
    int result = 0;
    if( m_programs.contains( programID ) ) {
      Q_FOREACH( const ProgramData& data, m_programs[programID] ) {
        if( data.shaderType==shaderType ) {
          result = data.shaderID;
          break;
        }
      }
    }
    return result;
  }

  void attachShader( GLuint programID, GLuint shaderType, GLuint shaderID ) {
    GLuint oldShader = 0;
    QVector<ProgramData> dataSet = m_programs[programID];
    for( int idx = 0; idx < dataSet.count(); ++idx ) {
      if( dataSet.at( idx ).shaderType==shaderType ) {
        oldShader = dataSet.at( idx ).shaderID;
        dataSet.remove( idx );
        break;
      }
    }

    if( oldShader ) {
      glDetachShader( programID, oldShader );
      glDeleteShader( oldShader );
    }

    dataSet << ProgramData(shaderType, shaderID);
    glAttachShader( programID, shaderID );
    m_complete[programID] = false;
  }

  bool linkProgram( GLuint programID ) {
    if( m_complete[programID] ) {
      return true;
    }

    glLinkProgram( programID );

    GLint status;
    glGetProgramiv( programID, GL_LINK_STATUS, &status );
    if( status != GL_TRUE ) {
      GLint len;
      glGetProgramiv( programID, GL_INFO_LOG_LENGTH, &len );

      char* info = new char[len];
      glGetProgramInfoLog( programID, len, &len, info );
      QMessageBox::warning( 0, "Link program", info );
      delete info;

      return false;
    }

    m_complete[programID] = true;
    return true;
  }

  QxGenericShader*                    m_self;

  QMap<GLuint, bool>                  m_complete;
  QMap<GLuint, QVector<ProgramData> > m_programs;
};

QxGenericShader* QxGenericShader::instance()
{
  static QxGenericShader* selfObject = 0;
  if( !selfObject ) {
    selfObject = new QxGenericShader();
  }

  return selfObject;
}

QxGenericShader::~QxGenericShader()
{
}

GLuint QxGenericShader::createProgram()
{
  GLuint result = glCreateProgram();
  _pd->m_programs[result] = QVector<ProgramData>();
  _pd->m_complete[result] = false;

  return result;
}

bool QxGenericShader::deleteProgram( GLuint programID )
{
  if( !_pd->m_programs.contains( programID  ) ) {
    return false;
  }

  Q_FOREACH( const ProgramData& data, _pd->m_programs[programID] ) {
    glDetachShader( programID, data.shaderID );
    glDeleteShader( data.shaderID );
  }

  _pd->m_programs.remove( programID );

  return true;
}

bool QxGenericShader::attachShader( GLuint programID, GLuint shaderType, const char* source )
{
  GLuint shaderID = glCreateShader( shaderType );
  glShaderSource( shaderID, 1, &source, 0 );
  glCompileShader( shaderID );

  GLint status;
  glGetShaderiv( shaderID, GL_COMPILE_STATUS, &status );
  if( status!=GL_TRUE ) {
    GLint len;
    glGetShaderiv( shaderID, GL_INFO_LOG_LENGTH, &len );

    char* info = new char[len];
    glGetShaderInfoLog( shaderID, len, &len, info );
    QMessageBox::warning( 0, "Compile shader", info );
    delete info;

    return false;
  }
  _pd->attachShader( programID, shaderType, shaderID );

  return true;
}

bool QxGenericShader::attachShader( GLuint programID, GLuint shaderType, const QString& filename )
{
  QFile hFile( filename );
  if( !hFile.open( QFile::ReadOnly ) ) {
    return false;
  }

  QTextStream fRead( &hFile );
  QString content = fRead.readAll();
  hFile.close();

  if( content.isEmpty() ) {
    return false;
  }

  return attachShader( programID, shaderType, content.toLocal8Bit().constData() );
}

bool QxGenericShader::useProgram( GLuint programID )
{
  if( !_pd->m_programs.contains( programID ) ) {
    return false;
  }

  if( !_pd->linkProgram( programID ) ) {
    return false;
  }

  glUseProgram( programID );

  return true;
}

QxGenericShader::QxGenericShader() : _pd( new QxGenericShaderPrivate( this ) )
{
}
